<?php
declare(strict_types=1);

namespace app\shop\service;

use app\shop\enums\goods\GoodsStockDeductType;
use app\shop\enums\notify\NotifyMessageType;
use app\shop\enums\order\OrderCrontabType;
use app\shop\enums\order\OrderPayStatus;
use app\shop\enums\order\OrderSource;
use app\shop\enums\order\OrderStatus;
use app\shop\enums\payment\PayWay;
use app\shop\exception\FailException;
use app\shop\exception\ParamsErrorException;
use app\shop\job\AutoCancelOrderJob;
use app\shop\job\AutoEvaluateJob;
use app\shop\job\AutoReceiveJob;
use app\shop\library\pay\PayProvider;
use app\shop\library\ShopHelper;
use app\shop\library\ShopLogHelper;
use app\shop\model\goods\GoodsSkuModel;
use app\shop\model\order\OrderCrontabModel;
use app\shop\model\order\OrderDetailModel;
use app\shop\model\order\OrderEvaluateModel;
use app\shop\model\order\OrderModel;
use Exception;
use think\facade\Db;
use think\facade\Queue;
use think\Model;
use Throwable;

class OrderService
{
    private OrderModel $model;

    public function __construct()
    {
        $this->model = new OrderModel();
    }

    /**
     * 生成订单号
     * @param string $prefix 订单号前缀
     * @return string
     */
    public function generateOrderNo(string $prefix = ''): string
    {
        return $prefix . date('YmdHis') . mt_rand(1000000000, 9999999999);
    }

    /**
     * 订单支付前操作
     * @param array $params
     * <br>order_id 订单编号
     * @return OrderModel|array|mixed|Model
     * @throws FailException
     * @throws ParamsErrorException
     */
    public function beforePay(array $params): mixed
    {
        $order = $this->model->where('id', $params['order_id'])->with(['detail'])->findOrEmpty();
        if ($order->isEmpty()) {
            throw new ParamsErrorException('订单不存在或已被删除');
        }
        if ($order->status != OrderStatus::TO_PAT) {
            throw new FailException('订单状态异常');
        }
        if ($order->pay_status != OrderPayStatus::UNPAID) {
            throw new FailException('订单状态异常');
        }
        foreach ($order['detail'] as $detail) {
            $sku = GoodsSkuModel::where(['id' => $detail->sku_id])->findOrEmpty();
            if ($sku->isEmpty() || $sku->goods->status != 1) {
                throw new FailException("商品已下架");
            }
            if ($sku->goods->deduct_stock_type == GoodsStockDeductType::AFTER_PAY && $sku->stock < $detail->num) {
                throw new FailException("{$detail['goods_name']}[{$detail['spec']}]库存不足");
            }
        }
        return $order;
    }

    /**
     * 订单支付后操作
     * @param int $orderId
     * @return void
     * @throws FailException
     */
    public function afterPay(int $orderId): void
    {
        $order = $this->model->where('id', $orderId)->with(['detail'])->findOrEmpty();
        if ($order->isEmpty()) {
            throw new FailException('订单不存在或已被删除');
        }
        if ($order->pay_status == OrderPayStatus::PAID) {
            return;
        }

        $order->startTrans();
        try {
            // 维护商品库存和销量
            foreach ($order->detail as $detail) {
                $sku = GoodsSkuModel::where(['id' => $detail->sku_id])->lock(true)->findOrEmpty();
                if ($sku->isEmpty() || $sku->goods->status != 1) {
                    $this->cancelOrder([
                        'order_id'      => $order->id,
                        'cancel_reason' => '商品已下架'
                    ]);
                    throw new FailException("商品已下架");
                }
                $num = $detail->num;
                if ($sku->goods->deduct_stock_type == GoodsStockDeductType::AFTER_PAY) {
                    if ($sku->stock < $num) {
                        // 库存不足走订单取消流程
                        $this->cancelOrder([
                            'order_id'      => $order->id,
                            'cancel_reason' => '商品库存不足'
                        ]);
                        throw new FailException("商品库存不足");
                    }
                    $sku->setDec('stock', $num);
                    $sku->goods->setDec('total_stock', $num);
                }
                $sku->setInc('sales', $num);
                $sku->goods->setInc('sales_real', $num);
            }
            // 维护订单状态
            $order->status     = OrderStatus::TO_SHIPPED;
            $order->pay_status = OrderPayStatus::PAID;
            $order->pay_time   = time();
            $order->save();
            // 维护订单日志
            LogService::orderStatusLog($order->id, OrderStatus::TO_SHIPPED, '订单支付', $order->user_id);

            $this->deleteCrontab($orderId, OrderCrontabType::ORDER_CANCEL);

            // (new NotifyService())->send(NotifyMessageType::ORDER, $order->toArray());
            if($order->type == 2){
                $goodsid = Db::name('shop_order_detail')->where('order_id', $order->id)->value('goods_id');
                if(isset($order->play_user_id)){
                    $play_user_id = explode(',',$order->play_user_id);
                }else{
                    $play_user_id = [];
                }
                $number_of_people = Db::name('shop_goods')->where('id', $goodsid)->value('number_of_people');
                for ($i = 0; $i < $number_of_people; $i++) {
                    $data = [];
                    $data['order_id'] = $order->id;
                    if(isset($play_user_id[$i])){
                        $setting = Db::name('play_setting')->where('play_user_id',$play_user_id[$i])->find();
                        if(isset($setting) && $setting['switch'] == 1){
                            $data['play_user_id'] = (int)$play_user_id[$i];
                            $data['user_id'] = Db::name('play_user')->where('id', $data['play_user_id'])->value('shop_user_id');
                            if(isset($setting['proportion'])){
                                $data['commission'] = number_format($order->total_price * ($setting['proportion']/100), 2);
                                if($data['commission'] == 0){
                                    $data['commission'] = 0.01;
                                }
                            }
                            $data['status'] = 4;
                        }else{
                            $data['status'] = 1;
                        }
                    }else{
                        $data['status'] = 1;
                    }
                    $data['create_time'] = time();
                    $data['update_time'] = time();
                    Db::name('shop_order_play')->strict(false)->insert($data);
                }
            }
            $order->commit();
        } catch (FailException $e) {
            $order->rollback();
            throw new FailException($e->getMessage(), null, $e, ['params' => ['order_id' => $orderId]], true);
        }
    }

    /**
     * 订单取消
     * @param array $params
     * <br>order_id 订单ID
     * <br>operator_id 操作人ID
     * <br>is_admin 是否后台操作
     * <br>cancel_reason 取消原因
     * <br>cancel_remark 取消时后台审核原因
     * <br>refund_amount 取消时退款总金额
     * @return void
     * @throws Exception
     */
    public function cancelOrder(array $params): void
    {
        $orderId      = $params['order_id'] ?? 0;
        $operatorId   = $params['operator_id'] ?? 0;
        $cancelReason = $params['cancel_reason'] ?? '订单取消';
        $cancelRemark = $params['cancel_remark'] ?? '';
        $refundAmount = $params['refund_amount'] ?? 0;
        $isAdmin      = $params['is_admin'] ?? false;

        $order = $this->model->where([
            ['id', '=', $orderId]
        ])->with([
            'detail',
        ])->findOrEmpty();

        if ($order->isEmpty()) {
            return;
        }
        if ($order->status == OrderStatus::CANCEL) {
            return;
        }
        // 已付款的订单退款
        if ($order->pay_status == OrderPayStatus::PAID) {

            $payProvider = new PayProvider($order->pay_way);
            $payProvider->refund([
                'out_trade_no'  => $order->out_trade_no,
                'source'        => $order->source,
                'refund_amount' => $refundAmount ?: $order->pay_price,
                'total_amount'  => $order->pay_price,
            ]);

            $order->pay_status    = OrderPayStatus::REFUND;
            $order->refund_amount = $refundAmount;
            $order->refund_num    = $order->total_num;
        }
        // 回退库存
        if (in_array($order->status, [OrderStatus::TO_PAT, OrderStatus::TO_SHIPPED])) {
            foreach ($order->detail as $detail) {
                $detail->sku->setInc('stock', $detail->num);
                $detail->goods->setInc('total_stock', $detail->num);
            }
        }
        // 回退优惠券
        (new CouponService())->rollbackByOrderId($orderId);

        $order->status        = OrderStatus::CANCEL;
        $order->cancel_reason = $cancelReason;
        $order->cancel_remark = $cancelRemark;
        $order->cancel_time   = time();
        $order->save();

        if (!$operatorId) {
            $cancelOperate = '系统取消订单';
        } elseif ($isAdmin) {
            $cancelOperate = '后台取消订单';
        } else {
            $cancelOperate = '用户取消订单';
        }
        LogService::orderStatusLog($order->id, OrderStatus::CANCEL, $cancelOperate, $operatorId, $isAdmin);

        OrderCrontabModel::where('order_id', $orderId)->delete();
    }

    /**
     * 获取自动取消时间(时间戳)
     * @return int
     */
    public function getAutoCancelTime(): int
    {
        return intval(ShopHelper::get_setting('auto_cancel_time', 24) * 3600);
    }

    /**
     * 获取自动收货时间(时间戳)
     * @return int
     */
    public function getAutoReceiveTime(): int
    {
        return intval(ShopHelper::get_setting('auto_receive_time', 7) * 86400);
    }

    /**
     * 获取自动评价时间(时间戳)
     * @return int
     */
    public function getAutoEvaluateTime(): int
    {
        return intval(ShopHelper::get_setting('auto_evaluate_time', 7) * 86400);
    }

    /**
     * 订单可申请售后时间(时间戳)
     */
    public function getApplyRefundTime(): int
    {
        return intval(ShopHelper::get_setting('apply_refund_time', 7) * 86400);
    }

    /**
     * 订单发货后处理
     * @param int $orderId
     * @return void
     */
    public function afterDelivery(int $orderId): void
    {
        $autoReceiveTime = $this->getAutoReceiveTime();

        Queue::later($autoReceiveTime, AutoReceiveJob::class, ['order_id' => $orderId], AutoReceiveJob::$queue);

        $this->addCrontab($orderId, OrderCrontabType::ORDER_RECEIVE, $autoReceiveTime + time(), ['order_id' => $orderId]);
    }

    /**
     * 添加订单自动取消队列任务
     * @param int $orderId 订单ID
     * @return void
     */
    public function addAutoCancelJob(int $orderId): void
    {
        $autoCancelTime = $this->getAutoCancelTime();
        // 发布延时队列任务
        Queue::later($autoCancelTime, AutoCancelOrderJob::class, ['order_id' => $orderId], AutoCancelOrderJob::$queue);

        $this->addCrontab($orderId, OrderCrontabType::ORDER_CANCEL, $autoCancelTime + time(), ['order_id' => $orderId]);
    }

    /**
     * 订单收货后处理
     * @param int $orderId
     * @return void
     */
    public function afterReceive(int $orderId): void
    {
        $autoEvaluateTime = $this->getAutoEvaluateTime();

        Queue::later($autoEvaluateTime, AutoEvaluateJob::class, ['order_id' => $orderId], AutoEvaluateJob::$queue);

        $this->addCrontab($orderId, OrderCrontabType::ORDER_EVALUATE, $autoEvaluateTime + time(), ['order_id' => $orderId]);

        $this->deleteCrontab($orderId, OrderCrontabType::ORDER_RECEIVE);
    }

    /**
     * 订单评价后处理
     * @param int $orderId
     * @return void
     */
    public function afterEvaluate(int $orderId): void
    {
        $this->deleteCrontab($orderId, OrderCrontabType::ORDER_EVALUATE);
    }

    /**
     * 自动取消订单
     * @param int $orderId 订单ID
     * @return void
     * @throws ParamsErrorException
     * @throws Exception
     */
    public function autoCancel(int $orderId): void
    {
        $order = OrderModel::where('id', $orderId)->findOrEmpty();

        if ($order->isEmpty()) {
            throw new ParamsErrorException('订单不存在');
        }

        if ($order->status != OrderStatus::TO_PAT) {
            throw new ParamsErrorException('订单状态不为待付款');
        }

        if ($order->find_pay_time > time()) {
            throw new ParamsErrorException('订单支付未超时');
        }

        $this->model->startTrans();
        try {
            $this->cancelOrder([
                'order_id'      => $order->id,
                'operator_id'   => 0,
                'is_admin'      => false,
                'cancel_reason' => '订单超时未支付',
            ]);
            $this->deleteCrontab($order->id, OrderCrontabType::ORDER_CANCEL);

            $this->model->commit();
        } catch (Throwable $e) {
            $this->model->rollback();
            ShopLogHelper::error('自动取消订单失败', ['order_id' => $orderId, $e]);
            throw new Exception($e->getMessage());
        }
    }

    /**
     * 自动确认收货
     * @param int $orderId 订单ID
     * @return bool
     * @throws ParamsErrorException
     * @throws Exception
     */
    public function autoReceive(int $orderId): bool
    {
        $order = OrderModel::where(['id' => $orderId])->with(['paymentTrade'])->visible([
            'payment_trade' => ['trade_no']
        ])->findOrEmpty();

        if ($order->isEmpty()) {
            throw new ParamsErrorException('订单不存在');
        }

        if ($order->delivery_time <= 0 || $order->status != OrderStatus::TO_RECEIVED) {
            throw new ParamsErrorException('订单状态不可确认收货');
        }

        $this->model->startTrans();
        try {
            // 订单状态修改
            OrderModel::where(['id' => $orderId])->update([
                'status'       => OrderStatus::TO_EVALUATE,
                'receive_time' => time()
            ]);
            // 记录日志
            LogService::orderStatusLog($orderId, OrderStatus::TO_RECEIVED, '系统自动确认收货', 0);
            // 确认收货后操作
            $this->afterReceive($orderId);
            // 微信提醒用户确认收货
            if ($order->source == OrderSource::MINIAPP && $order->pay_way == PayWay::WXPAY && $order->payment_trade) {
                (new DeliveryService())->notifyConfirmReceive($order->payment_trade['trade_no']);
            }

            $this->model->commit();
        } catch (Throwable $e) {
            $this->model->rollback();
            ShopLogHelper::error('订单自动收货失败', ['order_id' => $orderId, $e]);
            throw new Exception($e->getMessage());
        }

        return true;
    }

    /**
     * 自动评价
     * @param int $orderId
     * @return bool
     * @throws Exception
     */
    public function autoEvaluate(int $orderId): bool
    {
        $order = OrderModel::where(['id' => $orderId])->with(['detail'])->findOrEmpty();

        if ($order->isEmpty()) {
            throw new ParamsErrorException('订单不存在');
        }
        if ($order->receive_time <= 0 || $order->status != OrderStatus::TO_EVALUATE) {
            throw new ParamsErrorException('订单状态不可评价');
        }

        $evaluateData = [];
        $user         = $order->user;
        foreach ($order->detail as $detail) {
            if ($detail['evaluate_time'] <= 0) {
                $evaluateData[] = [
                    'order_id'        => $order->id,
                    'order_detail_id' => $detail->id,
                    'goods_id'        => $detail->goods_id,
                    'goods_name'      => $detail->goods_name,
                    'goods_spec'      => $detail->spec,
                    'score'           => 5,
                    'status'          => 1,
                    'content'         => '此用户未填写评价内容',
                    'user_id'         => $user->id,
                    'user_name'       => $user->nickname,
                    'user_avatar'     => $user->avatar,
                ];
            }
        }

        if (empty($evaluateData)) {
            throw new ParamsErrorException('订单所有详情均已评价');
        }

        $this->model->startTrans();
        try {
            // 保存评价
            (new OrderEvaluateModel())->saveAll($evaluateData);
            // 更新订单状态
            OrderModel::update([
                'status'        => OrderStatus::COMPLETE,
                'evaluate_time' => time(),
            ], ['id' => $orderId]);
            // 更新订单状态日志
            LogService::orderStatusLog($orderId, OrderStatus::COMPLETE, '系统自动完成评价', 0);
            // 更新订单详情评价时间
            $orderDetailIds = array_column($evaluateData, 'order_detail_id');
            OrderDetailModel::where(['id' => $orderDetailIds])->update(['evaluate_time' => time()]);
            // 确认收货后处理
            $this->afterEvaluate($orderId);

            $this->model->commit();
        } catch (Throwable $e) {
            $this->model->rollback();

            throw new Exception($e->getMessage());
        }

        return true;
    }

    /**
     * 添加订单定时任务(存在则更新执行时间)
     * @param int|array $orderIds 订单ID
     * @param string    $action   定时任务行为
     * @param int       $time     定时任务执行时间
     * @param array     $data     定时任务数据
     * @return void
     */
    public function addCrontab(int|array $orderIds, string $action, int $time, array $data): void
    {
        if (!is_array($orderIds)) {
            $orderIds = [$orderIds];
        }

        foreach ($orderIds as $orderId) {
            $crontab = OrderCrontabModel::where('order_id', $orderId)->where('action', $action)->findOrEmpty();
            if ($crontab->isEmpty()) {
                OrderCrontabModel::create([
                    'order_id' => $orderId,
                    'action'   => $action,
                    'time'     => $time,
                    'data'     => $data
                ]);
            } else {
                $crontab->time = $time;
                $crontab->save();
            }
        }

    }

    /**
     * 删除订单定时任务
     * @param int|array $orderId
     * @param string    $action
     * @return void
     */
    public function deleteCrontab(int|array $orderId, string $action): void
    {
        OrderCrontabModel::where(['order_id' => $orderId])->where('action', $action)->delete();
    }
}